/*
 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.text.html;

import java.net.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;

/**
 * Component decorator that implements the view interface
 * for form elements, &lt;input&gt;, &lt;textarea&gt;,
 * and &lt;select&gt;.  The model for the component is stored
 * as an attribute of the the element (using StyleConstants.ModelAttribute),
 * and is used to build the component of the view.  The type
 * of the model is assumed to of the type that would be set by
 * <code>HTMLDocument.HTMLReader.FormAction</code>.  If there are
 * multiple views mapped over the document, they will share the
 * embedded component models.
 * <p>
 * The following table shows what components get built
 * by this view.
 * <table summary="shows what components get built by this view">
 * <tr>
 * <th>Element Type</th>
 * <th>Component built</th>
 * </tr>
 * <tr>
 * <td>input, type button</td>
 * <td>JButton</td>
 * </tr>
 * <tr>
 * <td>input, type checkbox</td>
 * <td>JCheckBox</td>
 * </tr>
 * <tr>
 * <td>input, type image</td>
 * <td>JButton</td>
 * </tr>
 * <tr>
 * <td>input, type password</td>
 * <td>JPasswordField</td>
 * </tr>
 * <tr>
 * <td>input, type radio</td>
 * <td>JRadioButton</td>
 * </tr>
 * <tr>
 * <td>input, type reset</td>
 * <td>JButton</td>
 * </tr>
 * <tr>
 * <td>input, type submit</td>
 * <td>JButton</td>
 * </tr>
 * <tr>
 * <td>input, type text</td>
 * <td>JTextField</td>
 * </tr>
 * <tr>
 * <td>select, size &gt; 1 or multiple attribute defined</td>
 * <td>JList in a JScrollPane</td>
 * </tr>
 * <tr>
 * <td>select, size unspecified or 1</td>
 * <td>JComboBox</td>
 * </tr>
 * <tr>
 * <td>textarea</td>
 * <td>JTextArea in a JScrollPane</td>
 * </tr>
 * <tr>
 * <td>input, type file</td>
 * <td>JTextField</td>
 * </tr>
 * </table>
 *
 * @author Timothy Prinzing
 * @author Sunita Mani
 */
public class FormView extends ComponentView implements ActionListener {

  /**
   * If a value attribute is not specified for a FORM input element
   * of type "submit", then this default string is used.
   *
   * @deprecated As of 1.3, value now comes from UIManager property FormView.submitButtonText
   */
  @Deprecated
  public static final String SUBMIT = new String("Submit Query");
  /**
   * If a value attribute is not specified for a FORM input element
   * of type "reset", then this default string is used.
   *
   * @deprecated As of 1.3, value comes from UIManager UIManager property FormView.resetButtonText
   */
  @Deprecated
  public static final String RESET = new String("Reset");

  /**
   * Document attribute name for storing POST data. JEditorPane.getPostData()
   * uses the same name, should be kept in sync.
   */
  final static String PostDataProperty = "javax.swing.JEditorPane.postdata";

  /**
   * Used to indicate if the maximum span should be the same as the
   * preferred span. This is used so that the Component's size doesn't
   * change if there is extra room on a line. The first bit is used for
   * the X direction, and the second for the y direction.
   */
  private short maxIsPreferred;

  /**
   * Creates a new FormView object.
   *
   * @param elem the element to decorate
   */
  public FormView(Element elem) {
    super(elem);
  }

  /**
   * Create the component.  This is basically a
   * big switch statement based upon the tag type
   * and html attributes of the associated element.
   */
  protected Component createComponent() {
    AttributeSet attr = getElement().getAttributes();
    HTML.Tag t = (HTML.Tag)
        attr.getAttribute(StyleConstants.NameAttribute);
    JComponent c = null;
    Object model = attr.getAttribute(StyleConstants.ModelAttribute);

    // Remove listeners previously registered in shared model
    // when a new UI component is replaced.  See bug 7189299.
    removeStaleListenerForModel(model);
    if (t == HTML.Tag.INPUT) {
      c = createInputComponent(attr, model);
    } else if (t == HTML.Tag.SELECT) {

      if (model instanceof OptionListModel) {

        JList list = new JList((ListModel) model);
        int size = HTML.getIntegerAttributeValue(attr,
            HTML.Attribute.SIZE,
            1);
        list.setVisibleRowCount(size);
        list.setSelectionModel((ListSelectionModel) model);
        c = new JScrollPane(list);
      } else {
        c = new JComboBox((ComboBoxModel) model);
        maxIsPreferred = 3;
      }
    } else if (t == HTML.Tag.TEXTAREA) {
      JTextArea area = new JTextArea((Document) model);
      int rows = HTML.getIntegerAttributeValue(attr,
          HTML.Attribute.ROWS,
          1);
      area.setRows(rows);
      int cols = HTML.getIntegerAttributeValue(attr,
          HTML.Attribute.COLS,
          20);
      maxIsPreferred = 3;
      area.setColumns(cols);
      c = new JScrollPane(area,
          JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
          JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    }

    if (c != null) {
      c.setAlignmentY(1.0f);
    }
    return c;
  }


  /**
   * Creates a component for an &lt;INPUT&gt; element based on the
   * value of the "type" attribute.
   *
   * @param set of attributes associated with the &lt;INPUT&gt; element.
   * @param model the value of the StyleConstants.ModelAttribute
   * @return the component.
   */
  private JComponent createInputComponent(AttributeSet attr, Object model) {
    JComponent c = null;
    String type = (String) attr.getAttribute(HTML.Attribute.TYPE);

    if (type.equals("submit") || type.equals("reset")) {
      String value = (String)
          attr.getAttribute(HTML.Attribute.VALUE);
      if (value == null) {
        if (type.equals("submit")) {
          value = UIManager.getString("FormView.submitButtonText");
        } else {
          value = UIManager.getString("FormView.resetButtonText");
        }
      }
      JButton button = new JButton(value);
      if (model != null) {
        button.setModel((ButtonModel) model);
        button.addActionListener(this);
      }
      c = button;
      maxIsPreferred = 3;
    } else if (type.equals("image")) {
      String srcAtt = (String) attr.getAttribute(HTML.Attribute.SRC);
      JButton button;
      try {
        URL base = ((HTMLDocument) getElement().getDocument()).getBase();
        URL srcURL = new URL(base, srcAtt);
        Icon icon = new ImageIcon(srcURL);
        button = new JButton(icon);
      } catch (MalformedURLException e) {
        button = new JButton(srcAtt);
      }
      if (model != null) {
        button.setModel((ButtonModel) model);
        button.addMouseListener(new MouseEventListener());
      }
      c = button;
      maxIsPreferred = 3;
    } else if (type.equals("checkbox")) {
      c = new JCheckBox();
      if (model != null) {
        ((JCheckBox) c).setModel((JToggleButton.ToggleButtonModel) model);
      }
      maxIsPreferred = 3;
    } else if (type.equals("radio")) {
      c = new JRadioButton();
      if (model != null) {
        ((JRadioButton) c).setModel((JToggleButton.ToggleButtonModel) model);
      }
      maxIsPreferred = 3;
    } else if (type.equals("text")) {
      int size = HTML.getIntegerAttributeValue(attr,
          HTML.Attribute.SIZE,
          -1);
      JTextField field;
      if (size > 0) {
        field = new JTextField();
        field.setColumns(size);
      } else {
        field = new JTextField();
        field.setColumns(20);
      }
      c = field;
      if (model != null) {
        field.setDocument((Document) model);
      }
      field.addActionListener(this);
      maxIsPreferred = 3;
    } else if (type.equals("password")) {
      JPasswordField field = new JPasswordField();
      c = field;
      if (model != null) {
        field.setDocument((Document) model);
      }
      int size = HTML.getIntegerAttributeValue(attr,
          HTML.Attribute.SIZE,
          -1);
      field.setColumns((size > 0) ? size : 20);
      field.addActionListener(this);
      maxIsPreferred = 3;
    } else if (type.equals("file")) {
      JTextField field = new JTextField();
      if (model != null) {
        field.setDocument((Document) model);
      }
      int size = HTML.getIntegerAttributeValue(attr, HTML.Attribute.SIZE,
          -1);
      field.setColumns((size > 0) ? size : 20);
      JButton browseButton = new JButton(UIManager.getString
          ("FormView.browseFileButtonText"));
      Box box = Box.createHorizontalBox();
      box.add(field);
      box.add(Box.createHorizontalStrut(5));
      box.add(browseButton);
      browseButton.addActionListener(new BrowseFileAction(
          attr, (Document) model));
      c = box;
      maxIsPreferred = 3;
    }
    return c;
  }

  private void removeStaleListenerForModel(Object model) {
    if (model instanceof DefaultButtonModel) {
      // case of JButton whose model is DefaultButtonModel
      // Need to remove stale ActionListener, ChangeListener and
      // ItemListener that are instance of AbstractButton$Handler.
      DefaultButtonModel buttonModel = (DefaultButtonModel) model;
      String listenerClass = "javax.swing.AbstractButton$Handler";
      for (ActionListener listener : buttonModel.getActionListeners()) {
        if (listenerClass.equals(listener.getClass().getName())) {
          buttonModel.removeActionListener(listener);
        }
      }
      for (ChangeListener listener : buttonModel.getChangeListeners()) {
        if (listenerClass.equals(listener.getClass().getName())) {
          buttonModel.removeChangeListener(listener);
        }
      }
      for (ItemListener listener : buttonModel.getItemListeners()) {
        if (listenerClass.equals(listener.getClass().getName())) {
          buttonModel.removeItemListener(listener);
        }
      }
    } else if (model instanceof AbstractListModel) {
      // case of JComboBox and JList
      // For JList, the stale ListDataListener is instance
      // BasicListUI$Handler.
      // For JComboBox, there are 2 stale ListDataListeners, which are
      // BasicListUI$Handler and BasicComboBoxUI$Handler.
      AbstractListModel listModel = (AbstractListModel) model;
      String listenerClass1 =
          "javax.swing.plaf.basic.BasicListUI$Handler";
      String listenerClass2 =
          "javax.swing.plaf.basic.BasicComboBoxUI$Handler";
      for (ListDataListener listener : listModel.getListDataListeners()) {
        if (listenerClass1.equals(listener.getClass().getName())
            || listenerClass2.equals(listener.getClass().getName())) {
          listModel.removeListDataListener(listener);
        }
      }
    } else if (model instanceof AbstractDocument) {
      // case of JPasswordField, JTextField and JTextArea
      // All have 2 stale DocumentListeners.
      String listenerClass1 =
          "javax.swing.plaf.basic.BasicTextUI$UpdateHandler";
      String listenerClass2 =
          "javax.swing.text.DefaultCaret$Handler";
      AbstractDocument docModel = (AbstractDocument) model;
      for (DocumentListener listener : docModel.getDocumentListeners()) {
        if (listenerClass1.equals(listener.getClass().getName())
            || listenerClass2.equals(listener.getClass().getName())) {
          docModel.removeDocumentListener(listener);
        }
      }
    }
  }

  /**
   * Determines the maximum span for this view along an
   * axis. For certain components, the maximum and preferred span are the
   * same. For others this will return the value
   * returned by Component.getMaximumSize along the
   * axis of interest.
   *
   * @param axis may be either View.X_AXIS or View.Y_AXIS
   * @return the span the view would like to be rendered into &gt;= 0. Typically the view is told to
   * render into the span that is returned, although there is no guarantee. The parent may choose to
   * resize or break the view.
   * @throws IllegalArgumentException for an invalid axis
   */
  public float getMaximumSpan(int axis) {
    switch (axis) {
      case View.X_AXIS:
        if ((maxIsPreferred & 1) == 1) {
          super.getMaximumSpan(axis);
          return getPreferredSpan(axis);
        }
        return super.getMaximumSpan(axis);
      case View.Y_AXIS:
        if ((maxIsPreferred & 2) == 2) {
          super.getMaximumSpan(axis);
          return getPreferredSpan(axis);
        }
        return super.getMaximumSpan(axis);
      default:
        break;
    }
    return super.getMaximumSpan(axis);
  }


  /**
   * Responsible for processing the ActionEvent.
   * If the element associated with the FormView,
   * has a type of "submit", "reset", "text" or "password"
   * then the action is processed.  In the case of a "submit"
   * the form is submitted.  In the case of a "reset"
   * the form is reset to its original state.
   * In the case of "text" or "password", if the
   * element is the last one of type "text" or "password",
   * the form is submitted.  Otherwise, focus is transferred
   * to the next component in the form.
   *
   * @param evt the ActionEvent.
   */
  public void actionPerformed(ActionEvent evt) {
    Element element = getElement();
    StringBuilder dataBuffer = new StringBuilder();
    HTMLDocument doc = (HTMLDocument) getDocument();
    AttributeSet attr = element.getAttributes();

    String type = (String) attr.getAttribute(HTML.Attribute.TYPE);

    if (type.equals("submit")) {
      getFormData(dataBuffer);
      submitData(dataBuffer.toString());
    } else if (type.equals("reset")) {
      resetForm();
    } else if (type.equals("text") || type.equals("password")) {
      if (isLastTextOrPasswordField()) {
        getFormData(dataBuffer);
        submitData(dataBuffer.toString());
      } else {
        getComponent().transferFocus();
      }
    }
  }


  /**
   * This method is responsible for submitting the form data.
   * A thread is forked to undertake the submission.
   */
  protected void submitData(String data) {
    Element form = getFormElement();
    AttributeSet attrs = form.getAttributes();
    HTMLDocument doc = (HTMLDocument) form.getDocument();
    URL base = doc.getBase();

    String target = (String) attrs.getAttribute(HTML.Attribute.TARGET);
    if (target == null) {
      target = "_self";
    }

    String method = (String) attrs.getAttribute(HTML.Attribute.METHOD);
    if (method == null) {
      method = "GET";
    }
    method = method.toLowerCase();
    boolean isPostMethod = method.equals("post");
    if (isPostMethod) {
      storePostData(doc, target, data);
    }

    String action = (String) attrs.getAttribute(HTML.Attribute.ACTION);
    URL actionURL;
    try {
      actionURL = (action == null)
          ? new URL(base.getProtocol(), base.getHost(),
          base.getPort(), base.getFile())
          : new URL(base, action);
      if (!isPostMethod) {
        String query = data.toString();
        actionURL = new URL(actionURL + "?" + query);
      }
    } catch (MalformedURLException e) {
      actionURL = null;
    }
    final JEditorPane c = (JEditorPane) getContainer();
    HTMLEditorKit kit = (HTMLEditorKit) c.getEditorKit();

    FormSubmitEvent formEvent = null;
    if (!kit.isAutoFormSubmission() || doc.isFrameDocument()) {
      FormSubmitEvent.MethodType methodType = isPostMethod
          ? FormSubmitEvent.MethodType.POST
          : FormSubmitEvent.MethodType.GET;
      formEvent = new FormSubmitEvent(
          FormView.this, HyperlinkEvent.EventType.ACTIVATED,
          actionURL, form, target, methodType, data);

    }
    // setPage() may take significant time so schedule it to run later.
    final FormSubmitEvent fse = formEvent;
    final URL url = actionURL;
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        if (fse != null) {
          c.fireHyperlinkUpdate(fse);
        } else {
          try {
            c.setPage(url);
          } catch (IOException e) {
            UIManager.getLookAndFeel().provideErrorFeedback(c);
          }
        }
      }
    });
  }

  private void storePostData(HTMLDocument doc, String target, String data) {

        /* POST data is stored into the document property named by constant
         * PostDataProperty from where it is later retrieved by method
         * JEditorPane.getPostData().  If the current document is in a frame,
         * the data is initially put into the toplevel (frameset) document
         * property (named <PostDataProperty>.<Target frame name>).  It is the
         * responsibility of FrameView which updates the target frame
         * to move data from the frameset document property into the frame
         * document property.
         */

    Document propDoc = doc;
    String propName = PostDataProperty;

    if (doc.isFrameDocument()) {
      // find the top-most JEditorPane holding the frameset view.
      FrameView.FrameEditorPane p =
          (FrameView.FrameEditorPane) getContainer();
      FrameView v = p.getFrameView();
      JEditorPane c = v.getOutermostJEditorPane();
      if (c != null) {
        propDoc = c.getDocument();
        propName += ("." + target);
      }
    }

    propDoc.putProperty(propName, data);
  }

  /**
   * MouseEventListener class to handle form submissions when
   * an input with type equal to image is clicked on.
   * A MouseListener is necessary since along with the image
   * data the coordinates associated with the mouse click
   * need to be submitted.
   */
  protected class MouseEventListener extends MouseAdapter {

    public void mouseReleased(MouseEvent evt) {
      String imageData = getImageData(evt.getPoint());
      imageSubmit(imageData);
    }
  }

  /**
   * This method is called to submit a form in response
   * to a click on an image -- an &lt;INPUT&gt; form
   * element of type "image".
   *
   * @param imageData the mouse click coordinates.
   */
  protected void imageSubmit(String imageData) {

    StringBuilder dataBuffer = new StringBuilder();
    Element elem = getElement();
    HTMLDocument hdoc = (HTMLDocument) elem.getDocument();
    getFormData(dataBuffer);
    if (dataBuffer.length() > 0) {
      dataBuffer.append('&');
    }
    dataBuffer.append(imageData);
    submitData(dataBuffer.toString());
    return;
  }

  /**
   * Extracts the value of the name attribute
   * associated with the input element of type
   * image.  If name is defined it is encoded using
   * the URLEncoder.encode() method and the
   * image data is returned in the following format:
   * name + ".x" +"="+ x +"&"+ name +".y"+"="+ y
   * otherwise,
   * "x="+ x +"&y="+ y
   *
   * @param point associated with the mouse click.
   * @return the image data.
   */
  private String getImageData(Point point) {

    String mouseCoords = point.x + ":" + point.y;
    int sep = mouseCoords.indexOf(':');
    String x = mouseCoords.substring(0, sep);
    String y = mouseCoords.substring(++sep);
    String name = (String) getElement().getAttributes().getAttribute(HTML.Attribute.NAME);

    String data;
    if (name == null || name.equals("")) {
      data = "x=" + x + "&y=" + y;
    } else {
      name = URLEncoder.encode(name);
      data = name + ".x" + "=" + x + "&" + name + ".y" + "=" + y;
    }
    return data;
  }

  /**
   * The following methods provide functionality required to
   * iterate over a the elements of the form and in the case
   * of a form submission, extract the data from each model
   * that is associated with each form element, and in the
   * case of reset, reinitialize the each model to its
   * initial state.
   */


  /**
   * Returns the Element representing the <code>FORM</code>.
   */
  private Element getFormElement() {
    Element elem = getElement();
    while (elem != null) {
      if (elem.getAttributes().getAttribute
          (StyleConstants.NameAttribute) == HTML.Tag.FORM) {
        return elem;
      }
      elem = elem.getParentElement();
    }
    return null;
  }

  /**
   * Iterates over the
   * element hierarchy, extracting data from the
   * models associated with the relevant form elements.
   * "Relevant" means the form elements that are part
   * of the same form whose element triggered the submit
   * action.
   *
   * @param buffer the buffer that contains that data to submit
   * @param targetElement the element that triggered the form submission
   */
  private void getFormData(StringBuilder buffer) {
    Element formE = getFormElement();
    if (formE != null) {
      ElementIterator it = new ElementIterator(formE);
      Element next;

      while ((next = it.next()) != null) {
        if (isControl(next)) {
          String type = (String) next.getAttributes().getAttribute
              (HTML.Attribute.TYPE);

          if (type != null && type.equals("submit") &&
              next != getElement()) {
            // do nothing - this submit is not the trigger
          } else if (type == null || !type.equals("image")) {
            // images only result in data if they triggered
            // the submit and they require that the mouse click
            // coords be appended to the data.  Hence its
            // processing is handled by the view.
            loadElementDataIntoBuffer(next, buffer);
          }
        }
      }
    }
  }

  /**
   * Loads the data
   * associated with the element into the buffer.
   * The format in which data is appended depends
   * on the type of the form element.  Essentially
   * data is loaded in name/value pairs.
   */
  private void loadElementDataIntoBuffer(Element elem, StringBuilder buffer) {

    AttributeSet attr = elem.getAttributes();
    String name = (String) attr.getAttribute(HTML.Attribute.NAME);
    if (name == null) {
      return;
    }
    String value = null;
    HTML.Tag tag = (HTML.Tag) elem.getAttributes().getAttribute
        (StyleConstants.NameAttribute);

    if (tag == HTML.Tag.INPUT) {
      value = getInputElementData(attr);
    } else if (tag == HTML.Tag.TEXTAREA) {
      value = getTextAreaData(attr);
    } else if (tag == HTML.Tag.SELECT) {
      loadSelectData(attr, buffer);
    }

    if (name != null && value != null) {
      appendBuffer(buffer, name, value);
    }
  }


  /**
   * Returns the data associated with an &lt;INPUT&gt; form
   * element.  The value of "type" attributes is
   * used to determine the type of the model associated
   * with the element and then the relevant data is
   * extracted.
   */
  private String getInputElementData(AttributeSet attr) {

    Object model = attr.getAttribute(StyleConstants.ModelAttribute);
    String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
    String value = null;

    if (type.equals("text") || type.equals("password")) {
      Document doc = (Document) model;
      try {
        value = doc.getText(0, doc.getLength());
      } catch (BadLocationException e) {
        value = null;
      }
    } else if (type.equals("submit") || type.equals("hidden")) {
      value = (String) attr.getAttribute(HTML.Attribute.VALUE);
      if (value == null) {
        value = "";
      }
    } else if (type.equals("radio") || type.equals("checkbox")) {
      ButtonModel m = (ButtonModel) model;
      if (m.isSelected()) {
        value = (String) attr.getAttribute(HTML.Attribute.VALUE);
        if (value == null) {
          value = "on";
        }
      }
    } else if (type.equals("file")) {
      Document doc = (Document) model;
      String path;

      try {
        path = doc.getText(0, doc.getLength());
      } catch (BadLocationException e) {
        path = null;
      }
      if (path != null && path.length() > 0) {
        value = path;
      }
    }
    return value;
  }

  /**
   * Returns the data associated with the &lt;TEXTAREA&gt; form
   * element.  This is done by getting the text stored in the
   * Document model.
   */
  private String getTextAreaData(AttributeSet attr) {
    Document doc = (Document) attr.getAttribute(StyleConstants.ModelAttribute);
    try {
      return doc.getText(0, doc.getLength());
    } catch (BadLocationException e) {
      return null;
    }
  }


  /**
   * Loads the buffer with the data associated with the Select
   * form element.  Basically, only items that are selected
   * and have their name attribute set are added to the buffer.
   */
  private void loadSelectData(AttributeSet attr, StringBuilder buffer) {

    String name = (String) attr.getAttribute(HTML.Attribute.NAME);
    if (name == null) {
      return;
    }
    Object m = attr.getAttribute(StyleConstants.ModelAttribute);
    if (m instanceof OptionListModel) {
      OptionListModel<Option> model = (OptionListModel<Option>) m;

      for (int i = 0; i < model.getSize(); i++) {
        if (model.isSelectedIndex(i)) {
          Option option = model.getElementAt(i);
          appendBuffer(buffer, name, option.getValue());
        }
      }
    } else if (m instanceof ComboBoxModel) {
      ComboBoxModel model = (ComboBoxModel) m;
      Option option = (Option) model.getSelectedItem();
      if (option != null) {
        appendBuffer(buffer, name, option.getValue());
      }
    }
  }

  /**
   * Appends name / value pairs into the
   * buffer.  Both names and values are encoded using the
   * URLEncoder.encode() method before being added to the
   * buffer.
   */
  private void appendBuffer(StringBuilder buffer, String name, String value) {
    if (buffer.length() > 0) {
      buffer.append('&');
    }
    String encodedName = URLEncoder.encode(name);
    buffer.append(encodedName);
    buffer.append('=');
    String encodedValue = URLEncoder.encode(value);
    buffer.append(encodedValue);
  }

  /**
   * Returns true if the Element <code>elem</code> represents a control.
   */
  private boolean isControl(Element elem) {
    return elem.isLeaf();
  }

  /**
   * Iterates over the element hierarchy to determine if
   * the element parameter, which is assumed to be an
   * &lt;INPUT&gt; element of type password or text, is the last
   * one of either kind, in the form to which it belongs.
   */
  boolean isLastTextOrPasswordField() {
    Element parent = getFormElement();
    Element elem = getElement();

    if (parent != null) {
      ElementIterator it = new ElementIterator(parent);
      Element next;
      boolean found = false;

      while ((next = it.next()) != null) {
        if (next == elem) {
          found = true;
        } else if (found && isControl(next)) {
          AttributeSet elemAttr = next.getAttributes();

          if (HTMLDocument.matchNameAttribute
              (elemAttr, HTML.Tag.INPUT)) {
            String type = (String) elemAttr.getAttribute
                (HTML.Attribute.TYPE);

            if ("text".equals(type) || "password".equals(type)) {
              return false;
            }
          }
        }
      }
    }
    return true;
  }

  /**
   * Resets the form
   * to its initial state by reinitializing the models
   * associated with each form element to their initial
   * values.
   *
   * param elem the element that triggered the reset
   */
  void resetForm() {
    Element parent = getFormElement();

    if (parent != null) {
      ElementIterator it = new ElementIterator(parent);
      Element next;

      while ((next = it.next()) != null) {
        if (isControl(next)) {
          AttributeSet elemAttr = next.getAttributes();
          Object m = elemAttr.getAttribute(StyleConstants.
              ModelAttribute);
          if (m instanceof TextAreaDocument) {
            TextAreaDocument doc = (TextAreaDocument) m;
            doc.reset();
          } else if (m instanceof PlainDocument) {
            try {
              PlainDocument doc = (PlainDocument) m;
              doc.remove(0, doc.getLength());
              if (HTMLDocument.matchNameAttribute
                  (elemAttr, HTML.Tag.INPUT)) {
                String value = (String) elemAttr.
                    getAttribute(HTML.Attribute.VALUE);
                if (value != null) {
                  doc.insertString(0, value, null);
                }
              }
            } catch (BadLocationException e) {
            }
          } else if (m instanceof OptionListModel) {
            OptionListModel model = (OptionListModel) m;
            int size = model.getSize();
            for (int i = 0; i < size; i++) {
              model.removeIndexInterval(i, i);
            }
            BitSet selectionRange = model.getInitialSelection();
            for (int i = 0; i < selectionRange.size(); i++) {
              if (selectionRange.get(i)) {
                model.addSelectionInterval(i, i);
              }
            }
          } else if (m instanceof OptionComboBoxModel) {
            OptionComboBoxModel model = (OptionComboBoxModel) m;
            Option option = model.getInitialSelection();
            if (option != null) {
              model.setSelectedItem(option);
            }
          } else if (m instanceof JToggleButton.ToggleButtonModel) {
            boolean checked = ((String) elemAttr.getAttribute
                (HTML.Attribute.CHECKED) != null);
            JToggleButton.ToggleButtonModel model =
                (JToggleButton.ToggleButtonModel) m;
            model.setSelected(checked);
          }
        }
      }
    }
  }


  /**
   * BrowseFileAction is used for input type == file. When the user
   * clicks the button a JFileChooser is brought up allowing the user
   * to select a file in the file system. The resulting path to the selected
   * file is set in the text field (actually an instance of Document).
   */
  private class BrowseFileAction implements ActionListener {

    private AttributeSet attrs;
    private Document model;

    BrowseFileAction(AttributeSet attrs, Document model) {
      this.attrs = attrs;
      this.model = model;
    }

    public void actionPerformed(ActionEvent ae) {
      // PENDING: When mime support is added to JFileChooser use the
      // accept value of attrs.
      JFileChooser fc = new JFileChooser();
      fc.setMultiSelectionEnabled(false);
      if (fc.showOpenDialog(getContainer()) ==
          JFileChooser.APPROVE_OPTION) {
        File selected = fc.getSelectedFile();

        if (selected != null) {
          try {
            if (model.getLength() > 0) {
              model.remove(0, model.getLength());
            }
            model.insertString(0, selected.getPath(), null);
          } catch (BadLocationException ble) {
          }
        }
      }
    }
  }
}
